fix(supabase): document the second entry point, and pin the runtime to the engine - #952
Conversation
…one runs in a Worker `docs/reference/supabase-sdk.md` still described the state before #912: one entry point, and a factory that "cannot run in a Worker or the browser". #912 added `@cipherstash/stack-supabase/wasm-inline`, which carries no Postgres driver and takes declared `schemas` instead of introspecting. Introspection was the only thing that needed a Postgres socket, so that entry does run in a Worker. The reference never said so. Corrects both halves: - the "One entry point" table now lists both, with the engine, how each learns the schema, and where each runs; - the factory paragraph now scopes its restriction to the native entry and points at the edge entry as the way to run in a Worker. The browser half of the old sentence was correct and is kept, with the reason named: the WASM client requires a workspace `clientKey` on every auth path (#804). Internal reference documentation — no package ships `docs/`, so no changeset. The same stale sentence is still live in `skills/stash-supabase/SKILL.md` and `packages/stack-supabase/README.md`, both of which DO ship; those are tracked separately. Claude-Session: https://claude.ai/code/session_01E1J2nVGJWVkqvLepDfinRf
🦋 Changeset detectedLatest commit: f31a202 The changes in this PR will be included in the next version bump. This PR includes changesets to release 11 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
The reference doc and three TSDoc blocks all derived the default entry's
runtime from schema discovery: introspection needs Postgres, therefore the
entry cannot run on an edge runtime. That inference is false in both
directions. Declaring `schemas` removes the Postgres dependency entirely
(create.ts:303-306, :364-367) and the entry is still Node-only; and the
entry would be Node-only with no introspection code in it at all.
What actually pins it is the import: `Encryption` from `@cipherstash/stack`
pulls a module graph that statically imports `@cipherstash/auth`, whose Node
entry resolves its platform binding at module evaluation, and the emitted
bundle carries an `import("pg")` specifier a bundler resolves at build time.
Neither moves when you declare schemas.
The default entry's doc also named `@cipherstash/protect-ffi` as the binary
loaded on import. It is the one package in that graph that deliberately does
not: `packages/protect-ffi/src/index.cts` uses `import native = require(...)`
specifically so `__importStar` cannot force the neon proxy to resolve, and
`nativeLoading.test.ts` guards it.
Two smaller corrections in the same pass: bare "a Worker" is ambiguous and
false under the Node `worker_threads` reading — the native entry runs fine
there — so the edge runtimes are now named, as the table already named them;
and the browser prohibition is restored to the native entry, which the
previous revision moved onto the edge entry, leaving the native paragraph
implying the browser was fine.
Guarded by scripts/__tests__/supabase-runtime-claims.test.mjs (three
detectors, unit-tested in both directions, applied to the four prose
sources), and by three new assertions in wasm-entry-edge-safety.test.ts that
tie the corrected prose to the emitted bundles — its header comment repeated
the protect-ffi misattribution and would otherwise have contradicted them.
Not touched, to avoid conflicting with open PRs: skills/stash-supabase and
packages/stack-supabase/README.md carry defect 1 verbatim but are being
rewritten on #951 at those exact lines, and the browser-capability claims in
examples/ and packages/stack/tsup.config.ts belong to #953. The README path
is recorded in the guard's GUARDED list comment so it is added when #951
lands.
Claude-Session: https://claude.ai/code/session_01FVKXa6GjUHN5xvJq2912KA
freshtonic
left a comment
There was a problem hiding this comment.
Approving — the corrections are right, I verified each load-bearing claim against the tree, and the guards are built the way this repo builds guards (detectors unit-tested in both directions, failure messages that say what to rewrite). One ask on the PR description below.
What I verified
- The protect-ffi/auth attribution is correct.
packages/protect-ffi/src/index.cts:17usesimport native = require('./load.cjs'),load.ctswraps every platform require in an arrow handed to@neon-rs/load, andnativeLoading.test.tsexists and guards it. So the import-timedlopenin the native graph does belong to@cipherstash/auth, and the old TSDoc naming protect-ffi was wrong exactly as the changeset says. - The new build-output gate points at a real file.
@cipherstash/stack'sexports["."].import.defaultis./dist/index.js, which is whatSTACK_ROOT_ENTRYresolves — sodescribeStackBuiltis a genuine skip-until-built, not a never-runs path. (Given this PR's own lecture on vacuous guards, that was the first thing I checked.) - The prose guard actually runs in CI:
scripts/__tests__/*.test.mjsis wired throughtest:scriptsalongside the existing guard suite. code()— used by the new auth-chain assertions — exists in the edge-safety test on this branch (line 40).- The GUARDED-list note about
README.mdis accurate, not stale: #951 is still open, so deferring the README path to post-merge avoids the two-branch conflict as described. - CI: everything green; the skips are the EQL path-filtered jobs, expected for a PR that doesn't touch
packages/eql.
The "unwrap inline code spans instead of deleting them" note in prose() is a nice catch — deleting spans would have disarmed the protect-ffi detector on every file, since the package name only ever appears in backticks.
The one ask: the PR description contradicts the diff
The Summary and Verification sections still describe the docs-only version of this branch: "docs/ ships in no package … so there is no changeset and nothing to rebuild" and "This PR deliberately does not touch them: they need changesets and a rebuild, and this one needs neither." The PR now ships a @cipherstash/stack-supabase patch changeset, TSDoc changes in three src/ files (which ship as .d.ts), and two guard tests. The changeset itself is accurate — it's the body that's frozen at an earlier scope. Please refresh it before merging; this repo mines PR bodies for provenance, and this one currently asserts the absence of a file it contains.
Non-blocking, for #951 rather than here
The GUARDED comment notes #951's branch "keeps the false so while dropping the browser half" — i.e. the causal inversion corrected here is about to land again in the README via #951, to be caught only when the README path is added to this guard afterwards. Cheaper to fix the sentence on the #951 branch now than to land it wrong and let the guard bounce it; worth a comment over there.
…t introspection The README and the Supabase skill both wrote "introspection needs a direct Postgres connection, so ... this entry runs on Node only". Introspection is not what makes the entry Node-only — it binds the native engine, and declaring schemas does not move that. The stated cause invites the conclusion that declaring tables makes the root entry edge-capable, which is the wrong turn the wasm-inline entry exists to prevent. This is the same causal inversion #952 corrected in the package's .d.ts. Two smaller instances in skills/stash-supabase/SKILL.md go with it: a bare "Worker" where the surrounding paragraph means any edge runtime, and the co-cause "and the engine is a native module, so ..." split into its own sentence so the engine carries the claim alone. Claude-Session: https://claude.ai/code/session_01133kxi8qXu6UJPiKJDwDt6
Summary
@cipherstash/stack-supabasewraps a Supabase client so that columns you have marked as encrypted are encrypted on the way into the database and decrypted on the way out. It has two entry points: the default one, which uses a native Node module to do the encryption, and@cipherstash/stack-supabase/wasm-inline, which uses a WebAssembly build instead so it can run on edge runtimes — Deno, Supabase Edge Functions, Cloudflare Workers.Our reference sheet, and the hover documentation that ships to users inside the package's type definitions, both explained why the default entry only runs on Node. The explanation was wrong. They said it was because the wrapper reads your table definitions out of the database at startup ("introspection"), which needs a direct Postgres connection. The real reason is the encryption engine it imports.
That matters because you can turn introspection off. Passing a
schemasoption makes the wrapper use the table definitions you hand it instead of reading them from the database, and then no Postgres connection is opened at all. Follow the old explanation and you would conclude that doing so lets you deploy the default entry to a Cloudflare Worker. It does not — it fails when the module is imported, before any of your code runs. The fix is to import thewasm-inlineentry, which is a different decision entirely.This started as a docs-only change to the reference sheet and grew: the same wrong explanation turned out to be in three source files whose comments ship to users as
.d.tstype definitions, so the PR now carries a patch changeset and two guard tests.Changes
Reference sheet (
docs/reference/supabase-sdk.md)schemasdoes not move it.worker_threadsworker.Shipped hover documentation (ships as
.d.ts, hence the changeset)src/create.ts— the comment on the exported factory said "declare your schemas and it runs anywhere". Replaced with "the entry point decides where this runs;schemasdecides only whether Postgres is involved."src/index.tsandsrc/create.tsboth named@cipherstash/protect-ffias the native module that loads on import. It is the one package in that graph that deliberately does not load on import; the module-evaluation-time load belongs to@cipherstash/auth. Corrected in both, with the second reason named too: the default entry's bundle carries animport("pg")specifier that a bundler resolves at build time.src/wasm-inline.ts— dropped "introspection is half of what made the default entry Node-only".Guards
scripts/__tests__/supabase-runtime-claims.test.mjs: three prose detectors, each unit-tested in both directions, applied to the four files above.packages/stack-supabase/__tests__/wasm-entry-edge-safety.test.tsgains three assertions tying the corrected prose to the emitted bundles. Its header comment claimed the stack root pulls both native packages but asserted nothing, so the negative assertions below it could have gone vacuous without failing. That comment repeated the same misattribution and is corrected.Changeset —
@cipherstash/stack-supabasepatch. Verified the corrected comments actually reach the published artifact: after a rebuild,dist/index.d.ts:535carries the new text.Verification
pnpm run test:scripts— 854 passed, 50 files. Includes the new guard (37 tests).pnpm --filter @cipherstash/stack-supabase test— 568 passed, 16 files.protect-ffi/src/load.cts, and revertingindex.ctstoimport * as nativeeach fail a distinct assertion. Tree restored and verified clean afterwards.scripts/__tests__/**/*.test.mjsis picked up bypnpm run test:scripts, invoked at.github/workflows/tests.yml:296in thelintjob on every pull request.pnpm run code:checkrepo-wide currently fails on the author's machine for an unrelated reason — ten stale.claude/worktrees/agent-*/biome.jsonfiles trigger a "nested root configuration" error. Those are excluded via.git/info/excluderather than.gitignore, so a fresh CI checkout never sees them. Scoped to the real directories (biome check packages scripts docs e2e examples skills biome-plugins, 861 files) it is 0 errors and 201 pre-existing warnings.The underlying claims were checked against source, not assumed:
packages/protect-ffi/src/index.cts:6-18(deliberately defers its binary load, guarded bynativeLoading.test.ts),packages/stack/src/index.ts:24(the static@cipherstash/authimport), andpackages/stack-supabase/src/create.ts:303-306and:364-367(declaringschemasskips introspection entirely).Related
Refs #912, #804, #708, #950.
Review notes
Start with
docs/reference/supabase-sdk.mdandsrc/create.ts— the latter is the comment users actually see on hover, so it is the highest-leverage correction here.Deliberately not fixed, to avoid conflicting with open PRs:
skills/stash-supabase/SKILL.mdandpackages/stack-supabase/README.mdcarry the same wrong explanation, and the README ships to customers. docs(supabase): document the wasm-inline entry in the two files that ship #951 is already rewriting those exact README lines, so editing them from two branches would conflict. The README path is recorded in the guard'sGUARDEDlist with a note to add it once docs(supabase): document the wasm-inline entry in the two files that ship #951 lands, so the guard will start enforcing it then.examples/supabase-workerandpackages/stack/tsup.config.tsbelong to docs(stack): stop advertising the WASM entry as browser-capable, and pin the reason against the core #953.One thing worth raising on #951 rather than here: its rewrite of those README lines keeps the same false "so" and drops the browser half. Cheaper to fix the sentence there before it lands than to let this guard bounce it afterwards.